(* DONT_TOUCH = "TRUE" *)
module i2c_dri
    #(
        parameter SLAVE_ADDR = 7'h2c, //从机地址
        parameter CLK_FREQ = 27'd100_000_000,//模块输入的时钟频率
        parameter I2C_FREQ = 18'd100_000 //I2C_SCL的时钟频率250Khz,所以驱动I2C时钟的是1Mhz，为它的四倍
    )
    (
        input   clk, //系统时钟
        input   rst_n, //系统复位

        //i2c interface
        input   i2c_exec, //I2C触发执行信号
        input   bit_ctrl, //字地址位控制（16/8） 1：16，0：8
        input   i2c_rh_wl, //I2C读写控制信号（1是读，0是写）
        input   [7:0]  i2c_addr, //器件内地址
        input   [7:0]   i2c_data_w, //I2C要写的数据
        output reg [7:0]   i2c_data_r, //I2C读取的数据
        output reg i2c_done, //I2C一次操作完成
        output reg i2c_ack, //I2C应答信号 0:应答 1:未应答
        output reg scl, //I2C的SCL时钟信号
        inout   sda, //I2C的SDA信号

        //user interface
        output reg dri_clk, //驱动I2C操作的驱动时钟
        output reg [7:0] cur_state
    );

//localparam define
localparam st_idle = 8'b0000_0001; //空闲状态
localparam st_sladdr = 8'b0000_0010; //发送器件地址(slave address)(控制位是写也就是0)
localparam st_addr16 = 8'b0000_0100; //发送16位字地址
localparam st_addr8 = 8'b0000_1000; //发送8位字地址
localparam st_data_wr = 8'b0001_0000; //写数据 8bit
localparam st_addr_rd = 8'b0010_0000; //发送器件地址读(和写的区别就是控制位的0和1)
localparam st_data_rd = 8'b0100_0000; //读数据 8bit
localparam st_stop = 8'b1000_0000; //结束I2C操作

//reg define
reg sda_dir; //SDA方向控制
reg sda_out; //SDA输出信号
reg st_done; //状态结束
reg wr_flag; //写标志
reg [6:0] cnt; //计数
reg [7:0] cur_state; //当前状态
reg [7:0] next_state; //下一状态
reg [15:0] addr_t; //字地址寄存
reg [7:0] data_r; //读取的数据
reg [7:0] data_wr_t; //I2C需写的数据的临时寄存
reg [9:0] clk_cnt; //分频时钟计数

//wire define
wire    sda_in;//SDA输入信号
wire [8:0] clk_divide;//模块驱动时钟分频系数





//SDA控制 三态门 是希望sda_dir在高的时候(主发送给从)，sda_out信号要拉高，sda_in要拉低，sda_dir在低的时候(从发送给主)，sda_out信号要变为高阻态Z且sda_in要拉高
assign sda = sda_dir ? sda_out : 1'bz;
assign sda_in = sda;
assign clk_divide = (CLK_FREQ / I2C_FREQ) >> 2'd2; //模块驱动时钟的分频系数,这儿操作等于除以4

//生成I2C的四倍频率的驱动时钟驱动IC2模块操作
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        dri_clk <= 1'b0;
        clk_cnt <= 10'b0;
    end
    else if(clk_cnt == clk_divide[8:1] - 9'd1) begin //这儿舍去最低位等于直接除以2，为了获取中间那个计数
        clk_cnt <= 10'd0;
        dri_clk <= ~dri_clk;
    end
    else begin
        clk_cnt <= clk_cnt + 10'b1;
    end
end
//时序逻辑状态转移
always @(posedge dri_clk or negedge rst_n) begin
    if (!rst_n)
        cur_state <= st_idle;
    else
        cur_state <= next_state;
end

//组合逻辑 状态转移条件
always @(*) begin
    next_state = st_idle;
    case(cur_state)
        st_idle: begin
            if(i2c_exec) begin //开始执行信号
                next_state = st_sladdr; //进入发送器件地址状态
            end
            else
                next_state = st_idle;
        end
        st_sladdr: begin
            if(st_done)begin //器件地址发送完成
                if(bit_ctrl) //检查字地址是8位还是16位
                    next_state = st_addr16; //16位就多发送高8位
                else
                    next_state = st_addr8;
            end
            else
                next_state = st_sladdr;
        end
        st_addr16: begin
            if(st_done) begin //高8位发送完成
                next_state = st_addr8; //进入发送低8位
            end
            else begin
                next_state = st_addr16;
            end
        end
        st_addr8: begin
            if(st_done) begin //低8位发送完成
                if(wr_flag == 1'b0) //读写判断
                    next_state = st_data_wr; //读
                else
                    next_state = st_addr_rd; //写
            end
            else begin
                next_state = st_addr8;
            end
        end
        st_data_wr: begin //写数据8bit
            if(st_done) //写数据完成
                next_state = st_stop; //结束
            else
                next_state = st_data_wr;
        end
        st_addr_rd:begin //写地址以进行读数据
            if(st_done) begin
                next_state = st_data_rd;
            end
            else begin
                next_state = st_addr_rd;
            end
        end
        st_data_rd: begin //读取数据8bit
            if(st_done) 
                next_state = st_stop;
            else
                next_state = st_data_rd; 
        end
        st_stop:begin //结束I2C操作
            if(st_done)
                next_state = st_idle;
            else
                next_state = st_stop;
        end
        default: next_state = st_idle;
    endcase
end

// 时序逻辑 状态输出
always @(posedge dri_clk or negedge rst_n) begin
    if (!rst_n) begin
        scl       <= 1'b1;
        sda_out   <= 1'b1;
        sda_dir   <= 1'b1;                          
        i2c_done  <= 1'b0;                          
        i2c_ack   <= 1'b0;                          
        cnt       <= 7'b0;                          
        st_done   <= 1'b0;                          
        data_r    <= 8'b0;                          
        i2c_data_r<= 8'b0;                          
        wr_flag   <= 1'b0;                          
        addr_t    <= 16'b0;                          
        data_wr_t <= 8'b0;  
    end
    else begin
        st_done <= 1'b0 ;  
        cnt <= cnt + 1'b1;
        case (cur_state)
            st_idle: begin //空闲状态 --------------------------------------------
                scl <= 1'b1;
                cnt <= 7'd0;
                st_done <= 1'b0;
                i2c_done<= 1'b0;
                sda_dir <= 1'b1;
                sda_out <= 1'b1;
                if(i2c_exec) begin                   
                    wr_flag   <= i2c_rh_wl ;         
                    addr_t    <= i2c_addr  ;         
                    data_wr_t <= i2c_data_w;  
                    i2c_ack   <= 1'b0;                      
                end            
            end
            st_sladdr: begin //写命令(期间地址和控制位) -----------------------------
                case(cnt)
                    7'd1 : sda_out <= 1'b0; //在scl高的时候拉低，开始I2C通信
                    7'd3 : scl <= 1'b0;
                    7'd4 : sda_out <= SLAVE_ADDR[6]; //发送器件地址的高7位
                    7'd5 : scl <= 1'b1;
                    7'd7 : scl <= 1'b0;
                    7'd8 : sda_out <= SLAVE_ADDR[5];
                    7'd9 : scl <= 1'b1;
                    7'd11 : scl <= 1'b0;
                    7'd12 : sda_out <= SLAVE_ADDR[4];
                    7'd13 : scl <= 1'b1;
                    7'd15 : scl <= 1'b0;
                    7'd16 : sda_out <= SLAVE_ADDR[3];
                    7'd17 : scl <= 1'b1;
                    7'd19 : scl <= 1'b0;
                    7'd20 : sda_out <= SLAVE_ADDR[2];
                    7'd21 : scl <= 1'b1;
                    7'd23 : scl <= 1'b0;
                    7'd24 : sda_out <= SLAVE_ADDR[1];
                    7'd25 : scl <= 1'b1;
                    7'd27 : scl <= 1'b0;
                    7'd28 : sda_out <= SLAVE_ADDR[0];
                    7'd29 : scl <= 1'b1;
                    7'd31 : scl <= 1'b0;
                    7'd32 : sda_out <= 1'b0; //0:写 1:读
                    7'd33 : scl <= 1'b1;
                    7'd35 : scl <= 1'b0;
                    7'd36 : begin //主机释放SDA使从机应答
                        sda_dir <= 1'b0; 
                        sda_out <= 1'b1;
                    end
                    7'd37 : scl <= 1'b1;
                    7'd38 : begin
                        st_done <= 1'b1;
                        if(sda_in == 1'b1) //高电平表示未应答
                            i2c_ack <= 1'b1; //拉高应答标志位
                    end
                    7'd39 : begin
                        scl <= 1'b0;
                        cnt <= 7'b0;
                    end
                    default : ;
                endcase
            end
            st_addr16: begin //发送16位字地址(只发高八位) --------------------------------
                case(cnt)
                    7'd0 : begin 
                        sda_out <= addr_t[15];
                        sda_dir <= 1'b1;
                    end
                    7'd1 : scl <= 1'b1;
                    7'd3 : scl <= 1'b0;
                    7'd4 : sda_out <= addr_t[14];
                    7'd5 : scl <= 1'b1;
                    7'd7 : scl <= 1'b0;
                    7'd8 : sda_out <= addr_t[13];
                    7'd9 : scl <= 1'b1;
                    7'd11 : scl <= 1'b0;
                    7'd12 : sda_out <= addr_t[12];
                    7'd13 : scl <= 1'b1;
                    7'd15 : scl <= 1'b0;
                    7'd16 : sda_out <= addr_t[11];
                    7'd17 : scl <= 1'b1;
                    7'd19 : scl <= 1'b0;
                    7'd20 : sda_out <= addr_t[10];
                    7'd21 : scl <= 1'b1;
                    7'd23 : scl <= 1'b0;
                    7'd24 : sda_out <= addr_t[9];
                    7'd25 : scl <= 1'b1;
                    7'd27 : scl <= 1'b0;
                    7'd28 : sda_out <= addr_t[8];
                    7'd29 : scl <= 1'b1;
                    7'd31 : scl <= 1'b0;
                    7'd32 : begin //主机释放SDA使从机应答
                        sda_dir <= 1'b0; 
                        sda_out <= 1'b1; //这儿置为1，如果从机应答了，sda就会得到0，被sda_in获取
                    end
                    7'd33 : scl <= 1'b1;
                    7'd34 : begin
                        st_done <= 1'b1;
                        if(sda_in == 1'b1)
                            i2c_ack <= 1'b1; //拉高应答标志位
                    end
                    7'd35 : begin
                        scl <= 1'b0;
                        cnt <= 7'b0;
                    end
                    default: ;
                endcase
            end
            st_addr8: begin //发送8位字地址(八位) -------------------------------------
                case(cnt)
                    7'd0 : begin 
                        sda_out <= addr_t[7];
                        sda_dir <= 1'b1;
                    end
                    7'd1 : scl <= 1'b1;
                    7'd3 : scl <= 1'b0;
                    7'd4 : sda_out <= addr_t[6];
                    7'd5 : scl <= 1'b1;
                    7'd7 : scl <= 1'b0;
                    7'd8 : sda_out <= addr_t[5];
                    7'd9 : scl <= 1'b1;
                    7'd11 : scl <= 1'b0;
                    7'd12 : sda_out <= addr_t[4];
                    7'd13 : scl <= 1'b1;
                    7'd15 : scl <= 1'b0;
                    7'd16 : sda_out <= addr_t[3];
                    7'd17 : scl <= 1'b1;
                    7'd19 : scl <= 1'b0;
                    7'd20 : sda_out <= addr_t[2];
                    7'd21 : scl <= 1'b1;
                    7'd23 : scl <= 1'b0;
                    7'd24 : sda_out <= addr_t[1];
                    7'd25 : scl <= 1'b1;
                    7'd27 : scl <= 1'b0;
                    7'd28 : sda_out <= addr_t[0];
                    7'd29 : scl <= 1'b1;
                    7'd31 : scl <= 1'b0;
                    7'd32 : begin //主机释放SDA使从机应答
                        sda_dir <= 1'b0; 
                        sda_out <= 1'b1; //这儿置为1，如果从机应答了，sda就会得到0，被sda_in获取
                    end
                    7'd33 : scl <= 1'b1;
                    7'd34 : begin
                        st_done <= 1'b1;
                        if(sda_in == 1'b1)
                            i2c_ack <= 1'b1; //拉高应答标志位
                    end
                    7'd35 : begin
                        scl <= 1'b0;
                        cnt <= 7'b0;
                    end
                    default: ;
                endcase
            end
            st_data_wr: begin                        //写数据(8 bit)---------------------------------
                case(cnt)                            
                    7'd0: begin                      
                        sda_dir <= 1'b1;
                        sda_out <= data_wr_t[7];     //I2C写8位数据
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= data_wr_t[6];  
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= data_wr_t[5];  
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= data_wr_t[4];  
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= data_wr_t[3];  
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= data_wr_t[2];  
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= data_wr_t[1];  
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= data_wr_t[0];  
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: begin                     //主机释放SDA使从机应答
                        sda_dir <= 1'b0;           
                        sda_out <= 1'b1;                              
                    end                              
                    7'd33: scl <= 1'b1;              
                    7'd34: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end          
                    7'd35: begin                     
                        scl  <= 1'b0;                
                        cnt  <= 7'b0;                
                    end                              
                    default  :  ;                    
                endcase                              
            end
            st_addr_rd: begin                        //写地址以进行读数据------------------------------
                case(cnt)                            
                    7'd0 : begin                     
                        sda_dir <= 1'b1;             
                        sda_out <= 1'b1;             
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd2 : sda_out <= 1'b0;          //重新开始
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= SLAVE_ADDR[5]; 
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= SLAVE_ADDR[4]; 
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= SLAVE_ADDR[3]; 
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= SLAVE_ADDR[2]; 
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= SLAVE_ADDR[1]; 
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= SLAVE_ADDR[0]; 
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: sda_out <= 1'b1;          //1:读
                    7'd33: scl <= 1'b1;              
                    7'd35: scl <= 1'b0;              
                    7'd36: begin                     
                        sda_dir <= 1'b0;            
                        sda_out <= 1'b1;                    
                    end
                    7'd37: scl     <= 1'b1;
                    7'd38: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end   
                    7'd39: begin
                        scl <= 1'b0;
                        cnt <= 7'b0;
                    end
                    default : ;
                endcase
            end
            st_data_rd: begin                        //读取数据(8 bit)-----------------------------
                case(cnt)
                    7'd0: sda_dir <= 1'b0;
                    7'd1: begin
                        data_r[7] <= sda_in;
                        scl       <= 1'b1;
                    end
                    7'd3: scl  <= 1'b0;
                    7'd5: begin
                        data_r[6] <= sda_in ;
                        scl       <= 1'b1   ;
                    end
                    7'd7: scl  <= 1'b0;
                    7'd9: begin
                        data_r[5] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd11: scl  <= 1'b0;
                    7'd13: begin
                        data_r[4] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd15: scl  <= 1'b0;
                    7'd17: begin
                        data_r[3] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd19: scl  <= 1'b0;
                    7'd21: begin
                        data_r[2] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd23: scl  <= 1'b0;
                    7'd25: begin
                        data_r[1] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd27: scl  <= 1'b0;
                    7'd29: begin
                        data_r[0] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd31: scl  <= 1'b0;
                    7'd32: begin
                        sda_dir <= 1'b1;             
                        sda_out <= 1'b1;
                    end
                    7'd33: scl     <= 1'b1;
                    7'd34: st_done <= 1'b1;          //非应答
                    7'd35: begin
                        scl <= 1'b0;
                        cnt <= 7'b0;
                        i2c_data_r <= data_r;
                    end
                    default  :  ;
                endcase
            end
            st_stop: begin                           //结束I2C操作---------------------------
                case(cnt)
                    7'd0: begin
                        sda_dir <= 1'b1;             //结束I2C
                        sda_out <= 1'b0;
                    end
                    7'd1 : scl     <= 1'b1;
                    7'd3 : sda_out <= 1'b1; // 在scl高电平的时候，拉高sda，产生停止信号
                    7'd15: st_done <= 1'b1;
                    7'd16: begin
                        cnt      <= 7'b0;
                        i2c_done <= 1'b1;            //向上层模块传递I2C结束信号
                    end
                    default  : ;
                endcase
            end
        endcase
    end
end

endmodule